package jpb;

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static jpb.Assert.notNull;

/**
 * <p/>
 * Instances of this class are supposed to be used as enumeration constants representing arguments that may be provided
 * to a specific JVM.
 * 
 * <p/>
 * Each JVM implementation specifies a set of supported JVM args that extend this class. Clients of the
 * {@link JavaProcessBuilder} may provide custom JVM args values via {@link jpb.JavaProcessBuilder#customArg(jpb.JvmArg, Object)}.
 * The provided JVM arg instance will be validated to make sure it's compatible with the underlying JVM and the provided
 * value will be validated by the argument.
 * 
 * @param <T> type of the internal representation of the argument
 * 
 * @author Dariusz Kordonski
 */
public abstract class JvmArg<T> implements Serializable {

    private static final ConcurrentMap<String, JvmArg<?>> allArgs = new ConcurrentHashMap<String, JvmArg<?>>();
    
    private final Class<? extends Jvm> jvmClass;
    private final Class<T> argumentClass;
    private final String name;
    
    
    protected JvmArg(Class<? extends Jvm> jvmClass, String name, Class<T> argClass) {
        this.jvmClass = notNull("jvmClass", jvmClass);
        this.name = notNull("name", name);
        this.argumentClass = notNull("argClass", argClass);
        allArgs.put(toString(), this);
    }

    /**
     * Return JVM owning this argument.
     * 
     * @return JVM of this argument
     */
    public final Class<? extends Jvm> jvm() {
        return jvmClass;
    }

    /**
     * Checks if this JVM arg belongs to a given <tt>jvm<tt>.
     * 
     * @param jvm
     *          - JVM instance to check
     * @return <code>true</code>, if this JVM arg belongs to <tt>jvm<tt>
     */
    public final boolean isOf(Jvm jvm) {
        return this.jvmClass.equals(jvm.getClass());
    }
    
    /**
     * Validate given JVM argument value.
     * 
     * @param argValue
     *              - JVM argument value to check
     * @throws InvalidJvmArgException if validation fails
     */
    protected abstract void validate(T argValue);
    
    /**
     * Convert given value to the target implementation used in command
     * executed to start the Java process.
     * 
     * @param argValue
     * @return
     */
    protected abstract CommandArg convert(T argValue);


    /**
     * Class of the argument instances in the JVM runtime.
     *
     * @return argument class
     */
    public final Class<T> argumentClass() {
        return argumentClass;
    }

    @Override
    public final boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (!getClass().equals(obj.getClass())) {
            return false;
        }
        JvmArg<?> other = (JvmArg<?>) obj;
        return jvmClass.equals(other.jvmClass) && name.equals(other.name);
    }

    @Override
    public final int hashCode() {
        return jvmClass.hashCode() + 37 * name.hashCode();
    }

    @Override
    public String toString() {
        return "JvmArg[jvm=" + jvmClass.getName() + ",name=" + name + ",argumentClass=" + argumentClass.getName() + "]";
    }
    
    private Object readResolve() throws ObjectStreamException {
        return allArgs.get(toString()); 
    }
    
    @Override
    protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
}
